import Firework from "./Firework";
import Particle from "./Particle";
import rand from "./rand";

export default class World {
  canvas: HTMLCanvasElement;
  ctx: CanvasRenderingContext2D | null;

  radius: number = 0;



  /**
   * 粒子
   * @date 4/10/2024 - 5:29:16 PM
   *
   * @type {Set<Particle>}
   */
  particles: Set<Particle> = new Set<Particle>();

  /**
   * 正在发射的烟花
   * @date 4/10/2024 - 5:29:37 PM
   *
   * @type {Set<Firework>}
   */
  fireworks: Set<Firework> = new Set<Firework>();

  width = 1;
  height = 1;
  offscreen: HTMLCanvasElement;
  offscreen_ctx: CanvasRenderingContext2D | null;

  get part_count(): number {
    return Math.floor(rand(100, 150));
  }

  get firework_hue() { return rand(this.hue_min, this.hue_max) };

  /**
   * 粒子初速度基准
   * 单位：像素/毫秒
   * 有: 粒子初速度 = 粒子初速度基准 +- partSpeedVariance
   * @date 4/10/2024 - 5:15:05 PM
   *
   * @type {number}
   */
  part_sp: number = 100 / 1000;

  /**
   * 粒子初速度误差
   * 单位：像素/毫秒
   * 有: 粒子初速度 = 粒子初速度基准 +- partSpeedVariance
   * @date 4/10/2024 - 5:15:37 PM
   *
   * @type {number}
   */
  part_sp_variance: number = 60 / 1000; // 粒子初速度误差，有粒子初速度 = partSpeed +|- partSpeedVariance

  /**
   * 空气摩擦系数
   * 有： 下一毫秒速度 = 当前速度 * (1 - 空气摩擦系数)
   * @date 4/10/2024 - 5:17:01 PM
   *
   * @type {number}
   */
  air_friction_factor: number = 0.00001;

  /**
   * 重力加速度
   * 有： 下一毫秒Y方向速度 = 当前Y方向速度 + 当前Y方向速度
   * @date 4/10/2024 - 5:18:56 PM
   *
   * @type {number}
   */
  gravity: number = 20 / 25600;

  wind = 50;
  hue_min = 0;
  hue_max = 150;
  hue_variance = 30;


  firework_height_variance = 100;

  flicker_density = 20;
  show_shockwave = true;

  show_target = false;
  max_target_radius = 8

  clear_alpha = 0.95;
  get line_width(): number { return rand(3, 5) }
  playrate = 2

  constructor(canvas: HTMLCanvasElement) {
    this.offscreen = document.createElement('canvas')
    this.offscreen_ctx = this.offscreen.getContext('2d');

    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
  };

  spawn_particles(x: number, y: number, hue: number) {
    let countdown = this.part_count;
    while (--countdown) {
      const particle = new Particle(this, x, y, hue);
      this.particles.add(particle);
    }
  }

  spawn_firework(start_x: number, start_y: number, dead_x: number, dead_y: number) {
    const firework = new Firework(this).shot(start_x, start_y, dead_x, dead_y);
    this.fireworks.add(firework);
    return firework;
  }

  clear() {
    this.particles.clear();
    this.fireworks.clear();
    this.ctx?.clearRect(0, 0, this.width, this.height);
    this.stop();
  }
  destory() {
    this.clear();
  }

  private _anim_req_id: number = 0;
  start() {
    let prev_time = 0;
    const loop_func = (time: number) => {
      if (prev_time !== 0) {
        this.update_once(time - prev_time);
        this.offscreen_ctx && this.render_once(this.offscreen_ctx);
      }
      this._anim_req_id = window.requestAnimationFrame(loop_func);
      prev_time = time;
    }
    this._anim_req_id = window.requestAnimationFrame(loop_func);
  }
  stop() {
    if (this._anim_req_id)
      window.cancelAnimationFrame(this._anim_req_id);
    this._anim_req_id = 0;
  }
  update_once(dt: number) {
    for (const i of this.fireworks) i.update(dt * this.playrate)
    for (const i of this.particles) i.update(dt * this.playrate)
  }
  render_once(ctx: CanvasRenderingContext2D) {
    if (!ctx) return;
    const { width, height } = this.canvas.getBoundingClientRect();
    if (
      this.width != Math.floor(width) ||
      this.height != Math.floor(height)
    ) {
      this.width = this.offscreen.width = this.canvas.width = Math.floor(width)
      this.height = this.offscreen.height = this.canvas.height = Math.floor(height)
    }
    ctx.clearRect(0, 0, this.width, this.height);
    ctx.globalCompositeOperation = 'source-over';
    ctx.fillStyle = `rgba(0, 0, 0, ${this.clear_alpha})`;
    ctx.fillRect(0, 0, this.width, this.height);
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';

    for (const i of this.fireworks) i.draw(ctx)

    ctx.globalCompositeOperation = 'lighter';
    for (const i of this.particles) i.draw(ctx)

    this.ctx?.clearRect(0, 0, this.width, this.height);
    this.ctx?.drawImage(this.offscreen, 0, 0)
  }
}